#include <archive.h>
#include <archive_entry.h>
#include <clocale>
#include <dirent.h>
#include <fcntl.h>
#include <map>
#include <napi/native_api.h>
#include <string>

#define LOG_TAG "ARCHIVE"
#include <hilog/log.h>

enum class ErrorCode {
    SUCCESS = 0,
    FILE_NOT_FOUND = -1,
    FILE_READ_ERROR = -2,
    FILE_WRITE_ERROR = -3,
    ARCHIVE_ERROR = -4,
    UNSUPPORTED_FORMAT = -5,
    INVALID_DIRECTORY = -6,
    CREATE_TEMP_FILE_ERROR = -7,
    RENAME_FILE_ERROR = -8,
};

static const std::unordered_map<int, const char *> error_messages = {
    {static_cast<int>(ErrorCode::SUCCESS), "Success"},
    {static_cast<int>(ErrorCode::FILE_NOT_FOUND), "File not found"},
    {static_cast<int>(ErrorCode::FILE_READ_ERROR), "File read error"},
    {static_cast<int>(ErrorCode::FILE_WRITE_ERROR), "File write error"},
    {static_cast<int>(ErrorCode::ARCHIVE_ERROR), "Archive processing error"},
    {static_cast<int>(ErrorCode::UNSUPPORTED_FORMAT), "Unsupported compression format"},
    {static_cast<int>(ErrorCode::INVALID_DIRECTORY), "Invalid directory"},
    {static_cast<int>(ErrorCode::CREATE_TEMP_FILE_ERROR), "Temporary file creation error"},
    {static_cast<int>(ErrorCode::RENAME_FILE_ERROR), "File renaming error"},
};

const char *error_code_to_message(ErrorCode code, const char *custom_message = nullptr) {
    auto it = error_messages.find(static_cast<int>(code));
    if (it != error_messages.end()) {
        return custom_message ? custom_message : it->second;
    }

    return "Unknown error";
}

static napi_value create_result(napi_env env, ErrorCode code, const char *custom_message = nullptr) {
    napi_value result;
    napi_create_object(env, &result);
    
    napi_value error_code;
    napi_create_int32(env, static_cast<int32_t>(code), &error_code);
    napi_set_named_property(env, result, "code", error_code);

    const char *message = error_code_to_message(code, custom_message);
    napi_value error_message;
    napi_create_string_utf8(env, message, NAPI_AUTO_LENGTH, &error_message);
    napi_set_named_property(env, result, "message", error_message);
    return result;
}

static napi_value list_result(napi_env env, std::string out_list) {
    
    napi_value result;
    napi_create_object(env, &result);
    napi_value error_code;
    napi_create_int32(env, static_cast<int32_t>(0), &error_code);
    napi_set_named_property(env, result, "code", error_code);
    
    napi_value error_message;
    napi_create_string_utf8(env, out_list.c_str(), NAPI_AUTO_LENGTH, &error_message);
    napi_set_named_property(env, result, "message", error_message);
    return result;
}

static napi_value Decompress(napi_env env, napi_callback_info info) {
    size_t argc = 2;
    napi_value argv[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    size_t in_file_size;
    char in_file_buf[256];
    napi_get_value_string_utf8(env, argv[0], in_file_buf, sizeof(in_file_buf), &in_file_size);
    std::string in_file(in_file_buf, in_file_size);

    size_t out_file_size;
    char out_file_buf[256];
    napi_get_value_string_utf8(env, argv[1], out_file_buf, sizeof(out_file_buf), &out_file_size);
    std::string out_file(out_file_buf, out_file_size);

    if (in_file.empty() || out_file.empty()) {
        return create_result(env, ErrorCode::FILE_NOT_FOUND);
    }

    // Ensure the output directory ends with a slash
    if (!out_file.empty() && out_file.back() != '/') {
        out_file += '/';
    }

    struct archive *a;
    struct archive *out;
    struct archive_entry *entry;
    int r;

    a = archive_read_new();
    archive_read_support_format_all(a);
    archive_read_support_filter_all(a);

    out = archive_write_disk_new();
    archive_write_disk_set_options(out, ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS);
    archive_write_disk_set_standard_lookup(out);

    int fd = open(in_file.c_str(), O_RDONLY);
    if (fd == -1) {
        return create_result(env, ErrorCode::FILE_NOT_FOUND);
    }

    struct stat statbuf;
    if (fstat(fd, &statbuf) == -1) {
        close(fd);
        return create_result(env, ErrorCode::FILE_READ_ERROR);
    }

    off_t len = statbuf.st_size;
    std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(len);

    ssize_t bytes_read = read(fd, data.get(), len);
    if (bytes_read != len) {
        close(fd);
        return create_result(env, ErrorCode::FILE_READ_ERROR);
    }
    close(fd);

    r = archive_read_open_memory(a, data.get(), len);
    if (r != ARCHIVE_OK) {
        return create_result(env, ErrorCode::ARCHIVE_ERROR);
    }

    const char *original_locale = setlocale(LC_CTYPE, nullptr);
    setlocale(LC_CTYPE, "en_US.UTF-8");

    const char *error_string;

    while (true) {
        r = archive_read_next_header(a, &entry);
        if (r != ARCHIVE_OK) {
            error_string = archive_error_string(a);
            if (error_string) {
                OH_LOG_ERROR(LOG_APP, "Unable to read next header: %{public}s", error_string);
            }
            break;
        }

        const char *current_path = archive_entry_pathname(entry);
        std::string full_output_path = out_file + current_path;
        archive_entry_set_pathname(entry, full_output_path.c_str());

        r = archive_write_header(out, entry);
        if (r != ARCHIVE_OK) {
            error_string = archive_error_string(a);
            OH_LOG_ERROR(LOG_APP, "Unable to write header for file: %{public}s, error: %{public}s", current_path, error_string);
        } else {
            const void *buff;
            size_t size;
            int64_t offset;
            while ((r = archive_read_data_block(a, &buff, &size, &offset)) == ARCHIVE_OK) {
                r = archive_write_data_block(out, buff, size, offset);
                if (r != ARCHIVE_OK) {
                    error_string = archive_error_string(a);
                    OH_LOG_ERROR(LOG_APP, "Unable to write data for file: %{public}s, error: %{public}s", current_path, error_string);
                    break;
                }
            }
            if (r == ARCHIVE_EOF) {
                r = ARCHIVE_OK; // Reset error code if EOF reached
            }
        }
        archive_entry_clear(entry);
    }

    archive_read_close(a);
    archive_read_free(a);
    archive_write_close(out);
    archive_write_free(out);

    setlocale(LC_CTYPE, original_locale);
    
    return create_result(env, r == ARCHIVE_EOF ? ErrorCode::SUCCESS : ErrorCode::ARCHIVE_ERROR, error_string);
}

bool is_directory(const std::string &path) {
    struct stat path_stat;
    return stat(path.c_str(), &path_stat) == 0 && S_ISDIR(path_stat.st_mode);
}

std::string get_name(const std::string &file) {
    const char *name = strrchr(file.c_str(), '/');
    return name ? std::string(name + 1) : file;
}

std::string get_dir(const std::string &file) {
    const char *last_slash = strrchr(file.c_str(), '/');
    return last_slash ? std::string(file.c_str(), last_slash - file.c_str()) : "";
}

void compress_file(struct archive *out, const std::string &file_path, const std::string &base_file_path) {
    struct stat st;
    if (stat(file_path.c_str(), &st) != 0) {
        OH_LOG_ERROR(LOG_APP, "Unable to stat file: %{public}s", file_path.c_str());
        return;
    }

    struct archive_entry *entry = archive_entry_new();
    archive_entry_set_pathname(entry, base_file_path.c_str());
    archive_entry_set_size(entry, st.st_size);
    archive_entry_set_filetype(entry, AE_IFREG);
    archive_entry_set_perm(entry, st.st_mode & 0777);
    archive_write_header(out, entry);

    int fd = open(file_path.c_str(), O_RDONLY);
    if (fd == -1) {
        OH_LOG_ERROR(LOG_APP, "Unable to open file: %{public}s", file_path.c_str());
        archive_entry_free(entry);
        return;
    }

    char buffer[1024 * 1024];
    ssize_t bytes_read;
    while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
        archive_write_data(out, buffer, bytes_read);
    }
    close(fd);
    archive_entry_free(entry);
}

void compress_directory(struct archive *a, struct archive *out, const std::string &dir_path, const std::string &base_dir) {
    DIR *dir = opendir(dir_path.c_str());
    if (dir == nullptr) {
        OH_LOG_ERROR(LOG_APP, "Unable to open directory: %{public}s", dir_path.c_str());
        return;
    }

    struct dirent *entry;
    bool is_empty = true;
    while ((entry = readdir(dir)) != nullptr) {
        if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
            is_empty = false;
            std::string sub_dir_path = dir_path + "/" + entry->d_name;
            std::string sub_base_dir = base_dir.empty() ? entry->d_name : base_dir + "/" + entry->d_name;
            compress_directory(a, out, sub_dir_path, sub_base_dir);
        } else if (entry->d_type == DT_REG) {
            is_empty = false;
            std::string file_path = dir_path + "/" + entry->d_name;
            std::string base_file_path = base_dir.empty() ? entry->d_name : base_dir + "/" + entry->d_name;
            compress_file(out, file_path, base_file_path);
        }
    }
    closedir(dir);

    if (is_empty) {
        struct archive_entry *entry = archive_entry_new();
        archive_entry_set_pathname(entry, base_dir.empty() ? get_name(dir_path).c_str() : base_dir.c_str());
        archive_entry_set_filetype(entry, AE_IFDIR);
        archive_entry_set_perm(entry, 0755);
        archive_write_header(out, entry);
        archive_entry_free(entry);
    }
}

static napi_value Compress(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value argv[3] = {nullptr};
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    size_t in_file_size;
    char in_file_buf[256];
    napi_get_value_string_utf8(env, argv[0], in_file_buf, sizeof(in_file_buf), &in_file_size);
    std::string in_file(in_file_buf, in_file_size);

    size_t out_file_size;
    char out_file_buf[256];
    napi_get_value_string_utf8(env, argv[1], out_file_buf, sizeof(out_file_buf), &out_file_size);
    std::string out_file(out_file_buf, out_file_size);

    size_t format_size;
    char format_buf[10];
    napi_get_value_string_utf8(env, argv[2], format_buf, sizeof(format_buf), &format_size);
    std::string format(format_buf, format_size);

    std::string dir_path = get_dir(out_file);
    std::string temp_file_path = dir_path.empty() ? "/tmp/temp_archive_XXXXXX" : dir_path + "/temp_archive_XXXXXX";

    int temp_fd = mkstemp(&temp_file_path[0]);
    if (temp_fd == -1) {
        return create_result(env, ErrorCode::CREATE_TEMP_FILE_ERROR);
    }

    struct archive *a;
    struct archive *out;
    a = archive_read_disk_new();
    archive_read_disk_set_standard_lookup(a);

    out = archive_write_new();
    if (format == "7z") {
        archive_write_set_format_7zip(out);
    } else if (format == "tar") {
        archive_write_set_format_pax_restricted(out);
    } else if (format == "zip") {
        archive_write_set_format_zip(out);
    } else if (format == "gz") {
        archive_write_set_format_gnutar(out);
        archive_write_add_filter_gzip(out);
    } else if (format == "xz") {
        archive_write_set_format_gnutar(out);
        archive_write_add_filter_xz(out);
    } else {
        return create_result(env, ErrorCode::UNSUPPORTED_FORMAT);
    }

    if (archive_write_open_fd(out, temp_fd) != ARCHIVE_OK) {
        close(temp_fd);
        return create_result(env, ErrorCode::ARCHIVE_ERROR, archive_error_string(out));
    }
    
    const char *original_locale = setlocale(LC_CTYPE, nullptr);
    setlocale(LC_CTYPE, "en_US.UTF-8");

    if (is_directory(in_file)) {
        if (in_file.back() == '/') {
            compress_directory(a, out, in_file.substr(0, in_file.size() - 1), "");
        } else {
            compress_directory(a, out, in_file, get_name(in_file));
        }
    } else {
        compress_file(out, in_file, get_name(in_file));
    }

    archive_write_close(out);
    archive_write_free(out);
    archive_read_free(a);
    close(temp_fd);
    
    setlocale(LC_CTYPE, original_locale);

    if (rename(temp_file_path.c_str(), out_file.c_str()) != 0) {
        return create_result(env, ErrorCode::RENAME_FILE_ERROR);
    }

    return create_result(env, ErrorCode::SUCCESS);
}

static napi_value getFileList(napi_env env, napi_callback_info info) {
    
    size_t argc = 2;
    napi_value argv[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    size_t in_file_size;
    char archive_path[256];
    napi_get_value_string_utf8(env, argv[0], archive_path, sizeof(archive_path), &in_file_size);
    std::string in_file(archive_path, in_file_size);

    size_t out_file_size;
    char target_path[256];
    napi_get_value_string_utf8(env, argv[1], target_path, sizeof(target_path), &out_file_size);
    std::string out_file(target_path, out_file_size);
    
    struct archive *a = archive_read_new();
    archive_read_support_format_all(a);
    archive_read_support_filter_all(a);
    //archive_read_set_options(a, "charset=UTF-8"); // 明确指定编码
    
    setlocale(LC_CTYPE, "en_US.UTF-8");

    if (archive_read_open_filename(a, archive_path, 10240) != ARCHIVE_OK) {
        fprintf(stderr, "Error opening archive: %s\n", archive_error_string(a));
        return NULL;
    }
    
    char* paths[256];
    int capacity = 0;
    int size = 0;
    
    std::string out_list;

    struct archive_entry* entry;
    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        const char* pathname = archive_entry_pathname(entry);
        out_list = out_list+strdup(pathname) +"-,";
        // 复制路径字符串
    }
    
    return list_result(env, out_list);
}

static napi_value  getFileData(napi_env env, napi_callback_info info){//获取单个文件

    size_t argc = 2;
    napi_value argv[2] = {nullptr};
    napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);

    size_t in_file_size;
    char archive_path[256];
    napi_get_value_string_utf8(env, argv[0], archive_path, sizeof(archive_path), &in_file_size);
    std::string in_file(archive_path, in_file_size);

    size_t out_file_size;
    char target_path[256];
    napi_get_value_string_utf8(env, argv[1], target_path, sizeof(target_path), &out_file_size);
    std::string out_file(target_path, out_file_size);


    struct archive *a = archive_read_new();
    archive_read_support_format_all(a);
    archive_read_support_filter_all(a);
    setlocale(LC_CTYPE, "en_US.UTF-8");

    if (archive_read_open_filename(a, archive_path, 10240) != ARCHIVE_OK) {
        fprintf(stderr, "Error opening archive: %s\n", archive_error_string(a));
        return nullptr;
    }

    struct archive_entry *entry;
    while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
        const char *entry_path = archive_entry_pathname(entry);
        if (strcmp(entry_path, target_path) == 0) {
            // 创建父目录
            char *last_sep = strrchr(target_path, '/');
            if (last_sep) {
                *last_sep = '\0';
                mkdir(target_path, 0755); // 简化处理，实际需递归创建
                *last_sep = '/';
            }
            // 写入文件
            FILE *fp = fopen(target_path, "wb");
            const void *buff;
            size_t size;
            off_t offset;
            while (archive_read_data_block(a, &buff, &size, &offset) == ARCHIVE_OK) {
                fwrite(buff, 1, size, fp);
            }
            fclose(fp);
            printf("Extracted: %s\n", target_path);
            break;
        }
    }

    archive_read_close(a);
    archive_read_free(a);


    return create_result(env, ErrorCode::SUCCESS);

}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"decompress", nullptr, Decompress, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"compress", nullptr, Compress, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"getFileList", nullptr, getFileList, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"getFileData", nullptr, getFileData, nullptr, nullptr, nullptr, napi_default, nullptr},

    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module mycompress = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "mycompress",
    .nm_priv = nullptr,
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&mycompress); }